#ifndef __PROTOCOL_UTIL_HPP__
#define __PROTOCOL_UTIL_HPP__

#include <iostream>
#include <sys/sendfile.h>
#include <string>
#include <map>
#include <unordered_map>
#include <string.h>
#include <sstream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "ThreadPool.hpp"

#define OK 200
#define NOT_FOUND 404

#define WEB_ROOT "/root/WorkSpace/web_server_/wwwroot"
#define HOME_PAGE "index.html"

#define VERSION "HTTP/1.1"

std::unordered_map<std::string, std::string> suffix_map= {
    {".html", "text/html"},
    {".htm", "text/html"},
    {".css", "text/css"},
    {".js", "application/x-javascript"}
};

class StringUtil{
public:
    static void MakeKV(std::map<std::string, std::string>& kv, std::string& str_)
    {
        std::size_t pos_ = str_.find(": ");
        if(std::string::npos == pos_){
            return;
        }
        std::string k_ = str_.substr(0, pos_);
        std::string v_ = str_.substr(pos_ + 2);
        
        kv.insert(make_pair(k_, v_));
    }

    static std::string InToString(int& code)
    {
		std::stringstream ss;
        ss << code;
        return ss.str();
    }

    static std::string CodeToDesc(int code)
    {
        
        switch(code){
            case 200:
                return "OK";
            case 404:
                return "NOT_FOUND";
            default:
                return "UNKONW";
        }
    }
    static std::string& SuffixToType(const std::string& suffix_)
    {
        return suffix_map[suffix_];
    }
};

class Request{
public:
    std::string rq_line;
    std::string rq_head;
    std::string blank;
    std::string rq_text;
    int resourse_size;
    std::map<std::string, std::string> head_kv;
    int content_length;
    std::string path;    
    std::string pragam;
    std::string method;
private:
    std::string uri;
    std::string version;
    bool cgi;//method == post     get -> ? in uri 
    std::string suffix;

public:
    Request()
		:blank("\n")
        ,cgi(false)
        ,path(WEB_ROOT)
        ,resourse_size(0)
        ,content_length(0)
        ,suffix(".html")
    {}
    
    bool IsCgi()
    {
        return cgi;
    }
    
    void SetResourseSize(int rs_)
    {
        resourse_size = rs_; 
    }

    void RequestLineParse()//分析请求行
    {
		std::stringstream ss(rq_line);
        ss>> method >> uri >> version;
    }
    
	void UriParse()//对uri进行解析
    {
        if(strncmp(method.c_str(), "GET", method.size()) == 0){
            
            std::size_t pos_ = uri.find('?');
            if(std::string::npos == pos_)
            {
                cgi = false;
                path += uri;
            }else
	    	{
                cgi = true;
                path += uri.substr(0, pos_);
                pragam = uri.substr(pos_ + 1);
                std::cout<<pragam<<std::endl; 
            }

        }
        else
        {
            path += uri;
        }

        if(path[path.size() - 1] == '/')
        {
            path += HOME_PAGE;
        }
        std::cout <<"path is : "<<path<<std::endl;
    }
	
    bool IsPathLegal()
    {
        //stat 命令
        struct stat st;//API接口, 可以返回文件的基本属性 mode, size, type等等
        if(stat(path.c_str(), &st) < 0){
            LOG(WARNING, "path is not found");
            return false;
        }
        if(S_ISDIR(st.st_mode)){//判断是否是目录
            path += "/";
            path += HOME_PAGE;
        }
        else if((st.st_mode & S_IXUSR) ||\
                (st.st_mode & S_IXGRP) ||\
                (st.st_mode & S_IXOTH))//是否具有可执行
        {
            cgi = true;
        }
        resourse_size = st.st_size;
        std::cout<<"resourse_size is : "<<resourse_size<<std::endl;
        int pos_ = path.rfind(".");
        if(pos_ != std::string::npos)
        {
            suffix = path.substr(pos_);
        }
        suffix = ".html";
        return true;
    }

    bool RequestHeadParse()
    {
        int start = 0;
        while(start < rq_head.size()){
            std::size_t pos = rq_head.find('\n', start);
            if(std::string::npos == pos)
            {
                break;
            }
            else{
                std::string sub_string_ = rq_head.substr(start, pos - start);
                if(!sub_string_.empty())
                {
                    LOG(INFO, "substr is not empty()");
                    StringUtil::MakeKV(head_kv, sub_string_);
                }
                else{
                    break;
                }
                start = pos + 1;
            }
        }
        return true;
    }

    std::string& GetSuffix()
    {
        return suffix;
    }

    int GetContenLength()
    {
        std::string cl_ = head_kv["Connect-Length"];
        std::cout<<"cl_ : "<<cl_<<std::endl;
        if(!cl_.empty())
        {
            std::stringstream ss(cl_);
            ss >> content_length;
        }
        return content_length;
    }

    bool IsMethodLegal()
    {
        if(strcasecmp(method.c_str(), "GET")==0 ||\
                strcasecmp(method.c_str(), "POST")==0){
            cgi = true;
			return true;
        }
        return false;
    }
    
    bool IsNeedRecvText()
    {
        if(strcasecmp(method.c_str(), "POST") == 0)
        {
            return true;
        }
        return false;
    }

    ~Request()
    {}
};

class Respones{
public:
    std::string rsq_line;
    std::string rsq_head;
    std::string blank;
    std::string rsq_text;
public:
    int code;
    int fd;

    Respones():blank("\n"),fd(-1),code(OK)
    {}
    
    void MakeStatusLine()
    {
        rsq_line = VERSION;
        rsq_line += " ";
        rsq_line += StringUtil::InToString(code);
        rsq_line += " ";
        rsq_line += StringUtil::CodeToDesc(code);
        rsq_line += "\n";
        std::cout<<rsq_line<<std::endl;
    }

    void MakeResponesHead(Request*& rq_)
    {
        rsq_head = "Content-Length: ";
        rsq_head += StringUtil::InToString(rq_->resourse_size);
        rsq_head += "\n";
        rsq_head += "Content-Type: ";
        std::string suffix_ = rq_->GetSuffix();
        rsq_head += StringUtil::SuffixToType(suffix_);
        rsq_head += "\n";
        std::cout<<rsq_head<<std::endl;
    }


    void OpenResource(Request*& rq_)
    {
        std::string path_ = rq_->path;
        fd = open(path_.c_str(), O_RDONLY);
        std::cout<<"fd : "<<fd<<std::endl;
    }

    ~Respones()
    {
        if(fd >= 0)
            close(fd);
    }
};

class Connect{
public:
    Connect(int sock_):sock(sock_)
    {
        std::cout<<"sock : "<<sock<<std::endl;
    }

    int ReadOneLine(std::string &line_)
    {
        char c = 'X';
        while(c != '\n'){
            ssize_t s = recv(sock, &c, 1, 0);
            //make '\r' '\r\n' to '\n'
            if(c == '\r'){
               recv(sock, &c, 1, MSG_PEEK);
                if(c == '\n')
                {
                    recv(sock, &c, 1, 0);
                }
                else{
                    c = '\n';
                }
            }
            line_ += c;
        }
        return line_.size();
    }

    int ReadRequestHead(std::string &head_)
    {
        head_ = "";
		std::string line_;
        while(line_ != "\n")
        {
            line_ = "";
            ReadOneLine(line_);
            head_ += line_;
        }
        LOG(INFO, "Head is Recved successful");
        std::cout << head_ <<std::endl;
    }

    void RecvRequestText(Request *& rq_, int len_)
    {
        char c_;
        int i_ = 0;
        while(i_ < len_){
            recv(sock, &c_, 1, 0);
            rq_->rq_text.push_back(c_);
            i_++;
            LOG(INFO, "循环");
        }
        if(strcasecmp(rq_->method.c_str(), "POST") == 0)
        {
            rq_->pragam = rq_->rq_text;
        }
    }

    void SendRespones(Respones *& rsq_, Request *& rq_, const bool& cgi_)
    {
            std::string& rsq_line_ = rsq_->rsq_line;
            std::string& rsq_head_ = rsq_->rsq_head;
            std::string& blank_ = rsq_->blank;
            
            send(sock, rsq_line_.c_str(), rsq_line_.size(), 0);
            send(sock, rsq_head_.c_str(), rsq_head_.size(), 0);
            send(sock, blank_.c_str(), blank_.size(), 0);
        
        if(cgi_){
            std::string& rsq_text_ = rsq_->rsq_text;
		    send(sock, rsq_text_.c_str(), rsq_text_.size(), 0);
            LOG(INFO, "send all the things");
        }
        else{
            int& fd_ = rsq_->fd;
            sendfile(sock, fd_, NULL, rq_->resourse_size);
            LOG(INFO, "send all the things");
        }
    }
    
    ~Connect()
    {
        if(sock <= 0)
            return;
        close(sock);
    }

private:
    int sock;
};


class Entry
{
public:
    static int ProcessNonCgi(Connect*& conn_, Request*& rq_, Respones*& rsq_)
    {
        int &code_ = rsq_->code;
        rsq_->MakeStatusLine();
        LOG(INFO, "status line is Okay");
        rsq_->MakeResponesHead(rq_);
        LOG(INFO, "respones line is Okay");
        rsq_->OpenResource(rq_);
        LOG(INFO, "opened the resourse");
        conn_->SendRespones(rsq_, rq_, false);
        LOG(INFO, "sended the respones");
    }
    
    static void ProcessCgi(Connect*& conn_, Request*& rq_, Respones*& rsq_)
    {
        int& code_ = rsq_->code;
        int input[2];
        int output[2];

        pipe(input);
        pipe(output);
        std::string&  pragam_ = rq_->pragam ; 
        pid_t id = fork();
        if(id < 0)
        {
            LOG(ERROR, "fork error");
            code_ = NOT_FOUND;
            return;
        }
        else if(id == 0)
        {
            close(input[1]);
            close(output[0]);
            std::string cl_env_ = "Content-Length=";
            int size_ = pragam_.size();
            cl_env_ += StringUtil::InToString(size_);
            putenv((char*)cl_env_.c_str());
            //子进程进程替换实现CGI服务功能
            //进程在进行文件替换的时候，不需要要替换文件描述符
            std::string &path_ = rq_->path;
            std::cout<<cl_env_<<std::endl;
            dup2(input[0], 0);
            dup2(output[1],1);
            // Make Son Process Duped
            execl(path_.c_str(), path_.c_str(), NULL);
            exit(1);
        }
        else
        {
            close(input[0]);
            close(output[1]);
            
            int total_ = 0;
            int size_ = pragam_.size();
            int curr_ = 0;
            const char* p_ = pragam_.c_str();
            while(total_ < size_ &&\
                    (curr_ = write(input[1], p_+total_, size_ - total_)) > 0){
                total_ += curr_;
            }
            std::cout<<"pragma size_ is : "<<total_<<std::endl;
            std::string& rsq_text_ = rsq_->rsq_text;
            char c;
            int ret = 1;
            while(ret == 1)
            {
                ret = read(output[0], &c, 1); 
                rsq_text_ += c;
            }
           waitpid(id, NULL, 0);
           close(input[1]);
           close(input[0]);
           rsq_->MakeStatusLine();
           rq_->SetResourseSize(rsq_text_.size());
           rsq_->MakeResponesHead(rq_);
           LOG(INFO, "respones line is Okay");
           conn_->SendRespones(rsq_, rq_, true);
        }
    }

    static int ProcessResponse(Connect*& conn_, Request*& rq_, Respones*& rsq_)
    {
        if(rq_->IsCgi()){
            LOG(INFO, "Is Cgi!!!!!");
            ProcessCgi(conn_, rq_, rsq_);
        }else
        {
            LOG(INFO, "Is Not Cgi");
            ProcessNonCgi(conn_, rq_, rsq_);
        }
    }

    static int HandlerProcess(int sock_)
    {
        LOG(INFO, "Handle Request Begin");
        Connect* conn_ = new Connect(sock_);
        Request* rq_ = new Request();
        Respones* rsp_ = new Respones();

        conn_->ReadOneLine(rq_->rq_line);
        LOG(INFO, "Read One Line");
        rq_->RequestLineParse();
        LOG(INFO, "Parse One Line");
        int& code_ = rsp_->code;
        if(!rq_->IsMethodLegal()){
            //返回一个异常状态码
            LOG(INFO, "Metho is not Legal");
            code_ = NOT_FOUND;
            goto end;
        }
        rq_->UriParse();
        //判断资源存不存在
        if(!rq_->IsPathLegal()){
            code_ = NOT_FOUND;
            LOG(ERROR, "Path is NOT_FOUND");
            goto end;
        }
        //资源合法
        LOG(INFO, "Path is Ok");
        conn_->ReadRequestHead(rq_->rq_head);
        if(rq_->RequestHeadParse()){
            LOG(INFO, "parse head done");
        }else{
            code_ = NOT_FOUND;
            goto end;
        }
        //只有post方法要读正文
        
        if(rq_->IsNeedRecvText()){
            LOG(INFO, "Need To Read Text");
            conn_->RecvRequestText(rq_, rq_->GetContenLength());    
        }
        else
            LOG(INFO, "NoNeed To Read Text");

        ProcessResponse(conn_, rq_, rsp_);

end:
        if(code_ != OK){
            //HandleError
        }
        delete conn_;
        delete rq_;
        delete rsp_;
        return -1;
    }
};

#endif


